//
//  ListRepository.swift
//  
//
//  Created by 孙长坦 on 2022/3/28.
//

import Foundation
import RxSwift
import RxRelay
import Logging
import DictionaryCoding

open class ListDataRepository<T: Codable & Equatable>: DataRepositoryProtocol {

    public var logger = Logger(label: "LibBase.ListRepository")

    open class var id: String {
        return "ListRepository"
    }
    
    private let disposeBag = DisposeBag()
    
    public var id: String {
        return Self.id
    }
    
    public var dataChangeCallBack: ((_ encodeData: Any?) -> Void)?
    
    public var itemChangeCallBack: ((_ encodeItem: Any?) -> Void)?
    
    public var data: [T]?
    
    public let dataObservable = PublishRelay<[T]?>()
    
    public let itemObservable = PublishRelay<T>()
    
    public let maxCount: Int?
    
    /// 去重
    public let distinct: Bool
    

    convenience required public init() {
        self.init(data: nil, maxCount: nil, distinct: false)
    }

    public init(data: [T]?, maxCount: Int? = nil, distinct: Bool = false) {
        
        self.data = data
        self.maxCount = maxCount
        self.distinct = distinct
        
        logger.logLevel = .debug
        
        dataObservable
            .subscribe(onNext: { [weak self] _ in
                guard let strongSelf = self else {
                    return
                }
                
                strongSelf.dataChangeCallBack?(strongSelf.getEncodeData())
            })
            .disposed(by: disposeBag)
        
        itemObservable
            .subscribe(onNext: { [weak self] item in
                guard let strongSelf = self else {
                    return
                }
            
                strongSelf.itemChangeCallBack?(strongSelf.encode(item: item))
            })
            .disposed(by: disposeBag)

    }
    
    public func distinct(item: T) {
        guard distinct else {
            return
        }
        
        data?.removeAll(where: { element in
            return element == item
        })
    }
    
    open func encode(item: T) -> Any {
        let encodeItem: [String: Any]? = try? CodableBuilder().createDictionaryEncoder()
            .encode(item)
        return encodeItem ?? item
    }
    
    open func decode(item: Any) -> T? {
        if let decodeItem = item as? T {
            return decodeItem
        } else if let dic = item as? [String: Any] {
            return try? CodableBuilder().createDictionaryDecoder()
                .decode(T.self, from: dic)
        } else {
            return nil
        }
    }
    
    open func save() {}

    open func set(encodeData: Any?) {
        if encodeData == nil || encodeData is NSNull {
            data = nil
        } else if let data = encodeData as? [Any] {
            self.data = data.compactMap({ element in
                return decode(item: element)
            })
        }

        save()
        
        dataObservable.accept(self.data)
    }
    
    open func update(encodeItem: Any) {
        guard let item = encodeItem as? [String: Any] else {
            return
        }
        
        guard let decodeItem = decode(item: item) else {
            return
        }
        
        guard let index = data?.firstIndex(where: { element in
            getKey(item: element) == getKey(item: decodeItem)
        }) else {
            return
        }
        
        data?[index]  = decodeItem
        
        save()
        
        itemObservable.accept(data![index])
    }
    
    open func update(itemId: String, key: String, value: Any?) {
        fatalError()
    }

    open func add(encodeItem: Any) {
        guard let decodeItem = decode(item: encodeItem) else {
            return
        }
        
        distinct(item: decodeItem)

        if data == nil {
            data = [decodeItem]
        } else {
            data!.append(decodeItem)
        }
        
        if let maxCount = maxCount, data!.count > maxCount {
            data!.removeSubrange(..<(data!.count - maxCount))
        }
        
        save()
        
        dataObservable.accept(self.data)
    }
    
    open func insert(encodeItem: Any) {
        guard let decodeItem = decode(item: encodeItem) else {
            return
        }
        
        distinct(item: decodeItem)
        
        if data == nil {
            data = [decodeItem]
        } else {
            data!.insert(decodeItem, at: 0)
        }
        
        if let maxCount = maxCount, data!.count > maxCount {
            data!.removeSubrange(maxCount...)
        }
        
        save()
        
        dataObservable.accept(self.data)
    }

    open func getEncodeData() -> Any? {
        guard let data = data else {
            return nil
        }

        return data.compactMap { element -> Any in
            return encode(item: element)
        }
    }

    open func getEncodeItem(index: Int) -> Any? {
        guard let data = data, index >= 0, index < data.count else {
            return nil
        }

        return encode(item: data[index])
    }
    
    open func getKey(item: T) -> String {
        fatalError()
    }
    
    open func getEncodeItem(itemId: String) -> Any? {
        guard let item = data?.first(where: { element in
            return getKey(item: element) == itemId
        }) else {
            return nil
        }
        
        return encode(item: item)
    }
    
    open func removeItem(itemId: String) {
        data?.removeAll(where:) { element in
            return getKey(item: element) == itemId
        }
        
        save()
        
        dataObservable.accept(self.data)
    }
}
